﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ObviousCode.UI.Aspect.PluginLibrary.Interfaces;
using System.Drawing;
using ObviousCode.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Common.Utils.Library.Collections;
using ObviousCode.UI.Aspect;
using ObviousCode.Aspect.PluginLibrary.Args;
using ObviousCode.Common.Utils.UI.Library;
using ObviousCode.Aspect.PluginLibrary.Exceptions;
using ObviousCode.UI.Aspect.PluginLibrary;
using ObviousCode.Aspect.PluginLibrary.StateHandler;
using ObviousCode.Common.Utils;
using ObviousCode.Common.Utils.Library.Dictionaries;
using ObviousCode.Common.Utils.Library.Dictionaries.Persistance;

namespace ObviousCode.Aspect.PluginLibrary.Abstract
{
    public abstract class BaseSurfaceObject : ISurfaceObject
    {
		#region Fields (3) 

        public Dictionary<string, Func<object[], object>> _eventHandlers;
                IAspectDesigner _host;
        IAspectItem _tag;

		#endregion Fields 

		#region Constructors (1) 

        public BaseSurfaceObject(IAspectDesigner host)
        {
            _eventHandlers = new Dictionary<string, Func<object[], object>>();

            Properties = new PropertyDictionary();

            InstanceKey = Guid.NewGuid().ToString();

            Theme = new BasicTheme();
            
            StateHandler = new BasicStateHandler();

            StateHandler.DragStarted += new EventHandler(StateHandler_DragStarted);
            StateHandler.Dragging += new EventHandler<ObviousCode.Common.Utils.EventArgs<PointF>>(StateHandler_Dragging);
            StateHandler.DragCompleted += new EventHandler(StateHandler_DragCompleted);
            StateHandler.SelectionStateChanged += new EventHandler(StateHandler_SelectionStateChanged);

            LoadDefaultHandlers();
            
            LoadHandlers();            

            _host = host;
        }

		#endregion Constructors 

		#region Properties (16) 

        public abstract string Description { get; }

        public bool HandlesDrag { get { return StateHandler.HandlesDrag; } }

        public bool HandlesHover { get { return StateHandler.HandlesHover; } }

        public bool HandlesSelect { get { return StateHandler.HandlesSelect; } }

        public ChartObjectImages Images { get; set; }

        public string InstanceKey { get; private set; }

        public bool IsDragging
        {
            get
            {
                return State == BaseItemState.Dragged;
            }
        }

        public bool IsInAffinativeDrag { get { return StateHandler.IsInAffinativeDrag; } }

        public abstract string Key { get; }

        public PointF Position { get { return StateHandler.Position; } }

        public PropertyDictionary Properties { get; private set; }

        public bool Selected
        {
            get { return StateHandler.State == BaseItemState.Selected; }
            set 
            {
                if (!HandlesSelect) return;

                StateHandler.RequestState(value ? BaseItemState.Selected : BaseItemState.Unselected);
            }
        }

        public virtual string State { get { return StateHandler.State; } }

        public IStateHandler StateHandler { get; set; }

        public IAspectItem Tag
        {
            get { return _tag; }
            set
            {
                if (_tag != null)
                {
                    _tag.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(_tag_PropertyChanged);
                }

                _tag = value;

                if (_tag != null)
                {
                    _tag.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_tag_PropertyChanged);
                }

                if (TagChanged != null)
                {
                    TagChanged(this, EventArgs.Empty);
                }
            }
        }

        public IUITheme Theme { get; set; }

		#endregion Properties 

		#region Delegates and Events (7) 

		// Events (7) 

        public event EventHandler DragCompleted;

        public event EventHandler<EventArgs<PointF>> Dragging;

        public event EventHandler DragStarted;

        public event EventHandler RefreshRequested;

        public event EventHandler SelectionStateChanged;

        public event EventHandler StateChanged;

         public event EventHandler TagChanged;

		#endregion Delegates and Events 

		#region Methods (36) 

		// Public Methods (11) 

        public void BeginAffinativeDrag()
        {
            if (!HandlesDrag) return;

            StateHandler.BeginAffinativeDrag();
        }

        public void ContinueAffinativeDrag(PointF pointF)
        {
            if (!HandlesDrag) return;

            StateHandler.ContinueAffinativeDrag(pointF);
        }

        public void EndAffinativeDrag()
        {
            if (!HandlesDrag) return;

            StateHandler.EndAffinativeDrag();
        }

                public RectangleF GetLastRenderedBounds(Graphics graphics)
        {
            return StateHandler.State == BaseItemState.Dragged ?
                    CalculateBounds(graphics, StateHandler.CurrentDragPosition) :
                    CalculateBounds(graphics, Position);            
        }

        public object HandleEvent(string eventKey, params object[] args)
        {
            return _eventHandlers[eventKey](args);
        }

        public virtual PropertyDictionary Persist()
        {
            PropertyDictionary surfaceObjectDictionary = new PropertyDictionary();

            surfaceObjectDictionary.Set("type", GetType().ToString());

            surfaceObjectDictionary.Set("assembly-qualified-type", GetType().AssemblyQualifiedName);

            surfaceObjectDictionary.Set("instance-key", InstanceKey);
            
            surfaceObjectDictionary.Set("state-handler", StateHandler.Persist());

            surfaceObjectDictionary.Set("theme-type", Theme.GetType().ToString());

            surfaceObjectDictionary.Set("tag-key", _tag.Id);

            return surfaceObjectDictionary;
                
        }

        public virtual RectangleF Render(Graphics graphics)
        {
            RectangleF bounds = new RectangleF(0f, 0f, 0f, 0f);
         
            try
            {
                bounds = CalculateBounds(graphics, Position);

                switch (StateHandler.State)
                {
                    case BaseItemState.Dragged:
                        DrawDraggedItems(graphics, bounds);
                        break;
                    case BaseItemState.Hovered:
                        DrawHoveredItem(graphics, bounds);
                        break;
                    case BaseItemState.Selected:
                        DrawSelectedItem(graphics, bounds);
                        break;
                    default:
                        DrawUnselectedItem(graphics, bounds);
                        break;                        
                }

                return bounds;
            }                               
            catch (Exception e)
            {
                AspectRenderingException exception = new AspectRenderingException(e);
                exception.GuiltyParty = this;
                throw exception;
            }
            finally
            {
                StateHandler.LastRenderedBounds = bounds;
            }
        }

        public void SetPosition(PointF point)
        {
            StateHandler.SetPosition(point);
        }

                public abstract void SetUp();

        public bool SupportsEvent(string eventKey)
        {
            return _eventHandlers.ContainsKey(eventKey);
        }

        public abstract void TearDown();
		// Protected Methods (12) 

        protected void AddHandler(string key, Func<object[], object> callback)
        {
            _eventHandlers[key] = callback;
        }

        protected virtual void DrawCenteredImageAndLabel(Graphics graphics, RectangleF bounds)
        {
            SizeF textSize = GetTextSize(graphics);
            SizeF stackSize = new SizeF(GetStackWidth(textSize), GetStackHeight(textSize));

            float stackX = (bounds.Left + bounds.Width / 2) - (stackSize.Width / 2);
            float stackY = (bounds.Top + bounds.Height / 2) - (stackSize.Height / 2);

            RectangleF stackBounds = new RectangleF(stackX, stackY, stackSize.Width, stackSize.Height);
            
            //graphics.DrawRectangle(new Pen(Color.Red), stackBounds.X, stackBounds.Y, stackBounds.Width, stackBounds.Height);
            
            float imageX = 0f, imageY = 0f;
            float textX = 0f, textY = 0f;

            if (Theme.Style == TextImageOrder.ImageFirst)
            {
                if (Theme.Stacking == Direction.Horizontal)
                {
                    //image text
                    imageX = stackX;
                    imageY = stackY;

                    textX = stackX + Theme.StackPadding + Images.Icon.Width;
                    textY = stackY;
                }
                else
                {
                    //image
                    //text
                    imageX = (stackBounds.Left + stackBounds.Width / 2) - (Images.Icon.Width / 2);
                    imageY = stackY;

                    textX = (stackBounds.Left + stackBounds.Width / 2) - (textSize.Width / 2);
                    textY = stackY + Images.Icon.Height + Theme.StackPadding;
                }
            }    
            else
            {
                if (Theme.Stacking == Direction.Horizontal)
                {
                    //text image
                    textX = stackX;
                    textY = stackY;

                    imageX = stackX + Theme.StackPadding + textSize.Width;
                    imageY = stackY;
                }
                else
                {
                    //text
                    //image                    
                    textX = (stackBounds.Left + stackBounds.Width / 2) - (textSize.Width / 2);
                    textY = stackY;

                    imageX = (stackBounds.Left + stackBounds.Width / 2) - (Images.Icon.Width / 2);
                    imageY = stackY + textSize.Height + Theme.StackPadding;
                }
            }

            graphics.DrawImage(Images.Icon, new PointF(imageX, imageY));
            graphics.DrawString(Tag.Name, Theme.LabelFont, Brushes.Black, new PointF(textX, textY));
        }

        protected virtual void DrawDraggedBorder(Graphics graphics, RectangleF bounds)
        {
            DrawBorder(graphics, Theme.DraggedBorderPen, bounds);
        }

        protected virtual void DrawDraggedItems(Graphics graphics, RectangleF bounds)
        {
            if (Theme.DrawItemAtOriginalPositionOnDrag)
            {
                DrawImageAndLabel(graphics, bounds);
            }

            if (Theme.BorderOnDragged)
            {
                RectangleF dragBounds = CalculateBounds(graphics, StateHandler.CurrentDragPosition);

                if (Theme.ShadowOnDragged)
                {
                    DrawShadow(graphics, dragBounds, Theme.DraggedShadowColor);
                }

            	DrawDraggedBorder(graphics, dragBounds);        
            }
        }

        protected virtual void DrawHoveredBorder(Graphics graphics, RectangleF bounds)
        {
            DrawBorder(graphics, Theme.HoverBorderPen, bounds);
        }

                protected virtual void DrawHoveredItem(Graphics graphics, RectangleF bounds)
        {            
            DrawImageAndLabel(graphics, bounds);

            if (Theme.BorderOnHovered)
            {
                if (Theme.ShadowOnHovered)
                {
                    DrawShadow(graphics, bounds, Theme.HoverShadowColor);
                }
                
                DrawHoveredBorder(graphics, bounds);
            }
        }

        //Assumes central position in bounds for all states
        protected virtual void DrawImageAndLabel(Graphics graphics, RectangleF bounds)
        {
            DrawCenteredImageAndLabel(graphics, bounds);
        }

        protected virtual void DrawSelectedBorder(Graphics graphics, RectangleF bounds)
        {
            DrawBorder(graphics, Theme.SelectedBorderPen, bounds);
        }

        protected virtual void DrawSelectedItem(Graphics graphics, RectangleF bounds)
        {
            DrawImageAndLabel(graphics, bounds);

            if (Theme.BorderOnSelected)
            {
                if (Theme.ShadowOnSelected)
                {
                    DrawShadow(graphics, bounds, Theme.SelectedShadowColor);
                }
                
            	DrawSelectedBorder(graphics, bounds);            
            }
        }

        protected virtual void DrawUnselectedBorder(Graphics graphics, RectangleF bounds)
        {
            DrawBorder(graphics, Theme.UnselectedBorderPen, bounds);
        }

        protected virtual void DrawUnselectedItem(Graphics graphics, RectangleF bounds)
        {                                    
            DrawImageAndLabel(graphics, bounds);

            if (Theme.BorderOnUnselected)
            {
                if (Theme.ShadowOnUnselected)
                {
                    DrawShadow(graphics, bounds, Theme.UnselectedShadowColor);
                }
                
            	DrawUnselectedBorder(graphics, bounds);
            }
        }

        public void Load(PropertyDictionary dictionary, IAspectItem tag)
        {

            InstanceKey = dictionary.StringFor("instance-key");

            StateHandler.Load(dictionary.DictionaryFor("state-handler"));

            Tag = tag;
        }

                               protected abstract void LoadHandlers();
		// Private Methods (13) 

         void _tag_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name")
            {
                RequestRefresh();
            }
        }

        private RectangleF CalculateBounds(Graphics graphics, PointF point)
        {
            if (Theme.BorderBehaviour == AspectPluginBorderBehaviour.SetSize)
            {
                return new RectangleF(
                        new PointF(point.X, point.Y), Theme.SetBorderSize);
            }
            else
            {
                SizeF textSize = GetTextSize(graphics);

                float width = GetStackWidth(textSize);
                float height = GetStackHeight(textSize);

                RectangleF bounds = new RectangleF(
                    point.X, point.Y, width, height
                    );

                bounds.Inflate(Theme.BorderInflate);

                return bounds;
            }
        }

        private void DrawBorder(Graphics graphics, Pen pen, RectangleF bounds)
        {
            if (Theme.RoundedBorder)
            {
                graphics.DrawPath(pen, RoundedRectangle.CreateRoundedRectanglePath(bounds, Theme.CornerRadius));
            }
            else
            {
                graphics.DrawRectangle(pen, bounds.X, bounds.Y, bounds.Width, bounds.Height);
            }
        }

        private void DrawShadow(Graphics graphics, RectangleF bounds, Color color)
        {
            RectangleF shadowBounds = new RectangleF(bounds.Location, bounds.Size);
            shadowBounds.Offset(Theme.ShadowOffset);

            if (Theme.RoundedBorder)
            {
                List<Elements> displayElements = new List<Elements> { Elements.Bottom, Elements.BottomRight, Elements.Right };
                graphics.DrawPath(new Pen(color, Theme.ShadowWidth), RoundedRectangle.CreateRoundedRectanglePath(shadowBounds, Theme.CornerRadius, displayElements));
            }
            else
            {
                graphics.DrawRectangle(new Pen(color, Theme.ShadowWidth), bounds.X, bounds.Y, bounds.Width, bounds.Height);
            }
        }

        private float GetStackHeight(SizeF textSize)
        {
            float height = Theme.Stacking == Direction.Horizontal ?
                Math.Max(Images.Icon.Height, textSize.Height) :
                Images.Icon.Height + Theme.StackPadding + textSize.Height;
            return height;
        }

        private float GetStackWidth(SizeF textSize)
        {
            float width = Theme.Stacking == Direction.Horizontal ?
                Images.Icon.Width + Theme.StackPadding + textSize.Width :
                Math.Max(Images.Icon.Width, textSize.Width);
            return width;
        }

        private SizeF GetTextSize(Graphics graphics)
        {
            SizeF textSize = graphics.MeasureString(Tag.Name, Theme.LabelFont);
            return textSize;
        }

                          private void LoadDefaultHandlers()
        {
            if (HandlesSelect)
            {
                AddHandler(AspectConstants.MouseMoveKey, StateHandler.OnMouseMoveStateHandler);
                AddHandler(AspectConstants.MouseDownKey, StateHandler.OnMouseDownStateHandler);
                AddHandler(AspectConstants.MouseUpKey, StateHandler.OnMouseUpStateHandler);
            }
        }

        private void RequestRefresh()
        {
            if (RefreshRequested != null)
            {
                RefreshRequested(this, EventArgs.Empty);
            }
        }

           void StateHandler_DragCompleted(object sender, EventArgs e)
        {            
            if (DragCompleted != null)
            {
                DragCompleted(this, e);
            }
        }

        void StateHandler_Dragging(object sender, ObviousCode.Common.Utils.EventArgs<PointF> e)
        {            
            if (Dragging != null)
            {
                Dragging(this, e);
            }
        }

        void StateHandler_DragStarted(object sender, EventArgs e)
        {            
            if (DragStarted != null)
            {
                DragStarted(this, e);
            }
        }

        void StateHandler_SelectionStateChanged(object sender, EventArgs e)
        {
            if (SelectionStateChanged != null)
            {
                SelectionStateChanged(this, e);
            }
        }

		#endregion Methods 
    }
}